<?php

class Model
{
	const db_connection_string = "sqlite:base.sqlite";

	private $fields = array();
	private $column_names = array();
	public $columns_with_ids = array();
	public $has_many = array();
	public $belongs_to = array();

	public function __construct($attributes = null)
	{
		// Get table column names
		$sql = "PRAGMA table_info({$this->table_name()})";
		$result = $this->db_request($sql);
		foreach($result as $row)
		{
			$this->column_names[] = $row["name"];
			// Foreign keys
			if (substr($row['name'], -3) == "_id")
				$this->columns_with_ids[] = $row['name'];
		}
		logme($this->column_names);
		logme($this->has_many);
		logme($this->columns_with_ids);
		if (isset($attributes))
		{
			$this->set_fields_from_array($attributes);
		}
	}

	private function set_fields_from_array($attributes)
	{
		foreach($attributes as $key => $value)
			$this->__set($key, $value);
	}

	function __get($name)
	{
		if (array_key_exists($name, $this->fields))
			// Plain variable (column)
			return $this->fields[$name];
		else if (in_array($name, $this->has_many))
		{
			// Has many
			logme("{$name} is foreign objects");
			$classname = ucfirst(substr($name, 0, -1));
			$class = new ReflectionClass($classname);
			return $class->getMethod("find")->invoke(NULL, $classname, array($this->our_name_as_foreign_id() => $this->id));
		}
		else if (in_array($name, $this->belongs_to)) 
		{
			// Belongs to
			$classname = ucfirst($name);
			$class = new ReflectionClass($classname);
			$results = $class->getMethod("find")->invoke(NULL, $classname, array("id" => $this->fields[$name. "_id"]));
			return $results[0];
		}
	}
	
	function __set($name, $value)
	{
		//TODO setter for has_many and belongs_to
		
		if (in_array($name, $this->column_names))
			$this->fields[$name] = $value;
		else if (in_array($name, $this->belongs_to))
		{
			// Belongs to
			// For example, $post->author = $new_author, where $post is $this
			$this->fields[$name . "_id"] = $value->id;
		}
		else if (in_array($name, $this->has_many))
		{
			// Has many
			// For example, $author->posts = $new_posts, where $author is $this
			$foreign_id_name = $this->our_name_as_foreign_id();  // "author_id"
			foreach($this->__get($name) as $object)
			{
				$property = new ReflectionProperty($classname, $foreign_id_name);
				$property->setValue($object, $value);				
			}	
		}
		else
			error("Wrong assignment: no column {$name} in {$this->table_name()}");
	}
	
	private function our_name_as_foreign_id()
	{
		return substr(strtolower($this->table_name()), 0, -1) . "_id";
	}
	
	public function save()
	{
		$keys = implode(', ', array_keys($this->fields));
		$values_array_escaped = array();
		foreach ($this->fields as $key => $value)
		{
			$values_array_escaped[] = "'" . sqlite_escape_string($value) . "'"; 
		}
		$values_escaped = implode(', ', $values_array_escaped);

		$sql = "INSERT OR REPLACE INTO {$this->table_name()} ({$keys}) VALUES ({$values_escaped})";
		self::db_request($sql);
	}


	/**
	*	Create active record object(s) by finding records in database
	*
	*	@access public
	*	@param string $classname name of the caller class
	*	@param array $args OR integer $args search for (WHERE) statement params, when array("column_name" => "value") [case 1], when integer, means search for a record with this id [case 2]
	*	@param string $order_by string for ORDERBY SQL command
	*	@param integer $limit limit query to receive only defined number of records
	*	@param integer $offset offset for limit
	*	@return array of $classname objects [case 1] or a single $classname object [case 2]
	*	@see find_by_sql()
	*
	*/
	static function find($classname, $args = null, $order_by = null, $limit = -1, $offset = -1)
	{
		if (isset($args))
		{
			if (is_integer($args))
				$where = "id = {$args}";
			else
				$where = self::args_to_key_value_string($args, " AND ");
		}
		$table = $classname."s";
		$sql = "SELECT * FROM {$table}";
		if (isset($where)) 
			$sql .= " WHERE {$where}";
		if (isset($order_by))
			$sql .= " ORDERBY {$order_by}";
		if ($limit >= 0)
			$sql .= " LIMIT {$limit}";
		if ($offset >= 0)
			$sql .= ", {$offset}";
		
		$found_array = self::find_by_sql($classname, $sql);
		if (is_integer($args) && count($found_array) == 1)
			return $found_array[0];
		else
			return $found_array;
	}

	/**
	*	Create active record objects by finding records in database with SQL query 
	*	
	*	@access public
	*	@param string $classname name of the caller class
	*	@param string $sql SQL query (NOT SAFE, NEEDS FILTERING!)
	*	@return array of $classname objects
	*	@see find()
	*
	*/
	static function find_by_sql($classname, $sql)
	{
		$result = self::db_request($sql);
		
		$found_array = array();
		while ($row = $result->fetch(PDO::FETCH_ASSOC))
		{
			$class = new ReflectionClass($classname);
			$instance = $class->newInstance();
			$instance->set_fields_from_array($row); //TODO change this to call setter with foreach?
			$found_array[] = $instance;
		}
		return $found_array;
	}

	// Private

	private function table_name()
	{	
		return get_class($this) . "s";
	}
	
	static private function args_to_key_value_string($args, $glue = ",")
	{
		$key_value_pairs = array();
		foreach($args as $key => $value)
			$key_value_pairs[] = sqlite_escape_string($key) . " = '" . sqlite_escape_string($value) . "'";
		return implode($glue, $key_value_pairs);
	}
	
	
	static private function db_request($sql)
	{
		logme($sql);
		$connection = new PDO(Config::db_connection_string);					
		$result = $connection->query($sql);		
		if (!$result)
		{
			error("database request failed.");
			logme(implode(" ", $connection->errorInfo()));
		}
		return $result;
	}
	
}


?>